@using System.IO;
@using System.Text;
@using System.Security.Cryptography;
@inject IFileReaderService fileReaderService

<h1>Hello, hash!</h1>

This demo calculates SHA256 using the .NET framework <a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256managed?view=netcore-2.1">hash function</a>.
<br />
The chunked version only loads a chunk at a time into the algorithm (using <a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hashalgorithm.transformblock?view=netcore-2.1">TransformBlock</a>),
<br />
whereas the full-ram version reads the entire file into ram before doing anything.
<br />
<br />
The full-ram version may block the interface for larger files unless an inferior buffersize is used.
<br />
In any case the full-ram version will use at least the same amount of ram as the file size.
<br />
For any API accepting a stream but not supporting ReadAsync, the full-ram version must be used.
<br />

<br />
<br />
<input type="file" @ref=inputElement />
<br />
<br />
<input type="checkbox" @bind=DebugOutput />
<label for="useDebugOutput">Debug output</label>
<br />
Transfer method : <br/>
<label>
    <input type="radio" name="txmethod" checked="@(GetTxMethodChecked(TxMethods.Classic))" @onclick="@(e => OnTxMethodChange(TxMethods.Classic))" />
    Classic 
</label>
<label>
    <input type="radio" name="txmethod" checked="@(GetTxMethodChecked(TxMethods.MemoryStream))" @onclick="@(e => OnTxMethodChange(TxMethods.MemoryStream))"/>
    Use MemoryStream
</label>
<label>
    <input type="radio" name="txmethod" checked="@(GetTxMethodChecked(TxMethods.Base64))" @onclick="@(e => OnTxMethodChange(TxMethods.Base64))" />
    Use Base64
</label>
    <br />



    <input type="checkbox" @bind=UseBufferSize />
    <label for="useBufferSizeCheck">Use buffer size</label>
    <br />

    <input type="number" @bind=BufferSize alt="buffersize" placeholder="Buffer size" />
    <br />
    <br />
    <button @onclick=@HashFile class="btn btn-primary">Hash SHA256 of file</button>

    <br />
    <br />

    <h3>Log output</h3>
<textarea style="max-width: 100%;" cols="50" rows="20">@Output</textarea>

    @code {
        ElementReference inputElement;
        IFileReaderRef reference;
        string Output { get; set; }
        public enum TxMethods
        {
            MemoryStream,Base64,Classic
        }

        TxMethods CurrentTxMethod { get; set; } = TxMethods.Classic;

        bool UseBufferSize { get; set; } = false;
        bool UseMemoryStream { get; set; } = false;
        bool UseBase64 { get; set; } = false;
        bool DebugOutput { get; set; } = true;

        int BufferSize { get; set; } = 4096 * 16;

        protected override async Task OnAfterRenderAsync(bool isFirstRender)
        {
            reference = fileReaderService.CreateReference(inputElement);
        }

        public bool GetTxMethodChecked(TxMethods value)
        {
            return value == CurrentTxMethod;
        }

        public void OnTxMethodChange(TxMethods value)
        {
            CurrentTxMethod = value;
            this.StateHasChanged();
        }

        public async Task HashFile()
        {
            try
            {
                await InnerHashFile();
            }
            catch (Exception e)
            {
                Output += e.ToString();
            }
        }

        public async Task InnerHashFile()
        {
            Output = string.Empty;
            this.StateHasChanged();
            var nl = Environment.NewLine;
            foreach (var file in await reference.EnumerateFilesAsync())
            {
                IFileInfo fileInfo = null;
                if (DebugOutput)
                {
                    Output += $"Method: {CurrentTxMethod}{nl}";
                    fileInfo = await file.ReadFileInfoAsync();
                    Output += $"{nameof(IFileInfo)}.{nameof(fileInfo.Name)}: {fileInfo.Name}{nl}";
                    Output += $"{nameof(IFileInfo)}.{nameof(fileInfo.Size)}: {fileInfo.Size}{nl}";
                    Output += $"{nameof(IFileInfo)}.{nameof(fileInfo.Type)}: {fileInfo.Type}{nl}";
                    Output += $"{nameof(IFileInfo)}.{nameof(fileInfo.LastModifiedDate)}: {fileInfo.LastModifiedDate?.ToString() ?? "(N/A)"}{nl}";
                    Output += $"Reading file...";
                    this.StateHasChanged();
                }
                var stopWatch = new System.Diagnostics.Stopwatch();

                stopWatch.Start();
                var outputBuffer = new StringBuilder();
                using (var hash = new SHA256Managed())
                {
                    if (CurrentTxMethod == TxMethods.MemoryStream)
                    {

                        using (var fs = UseBufferSize ?
                            await file.CreateMemoryStreamAsync(BufferSize) :
                            await file.CreateMemoryStreamAsync())
                        {
                            stopWatch.Stop();
                            Output += $"{stopWatch.ElapsedMilliseconds}ms: File read into memory. Hashing...";
                            await Task.Delay(100);
                            await this.InvokeAsync(this.StateHasChanged);
                            await Task.Delay(100);
                            stopWatch.Start();
                            hash.ComputeHash(fs);
                        }
                    }
                    else
                    {
                        var lastAnnounce = 0m;
                        if (CurrentTxMethod == TxMethods.Base64)
                        {
                            using (var fs = await file.OpenReadBase64Async())
                            {
                                var bufferSize = UseBufferSize ? BufferSize : 4096 * 32;
                                if (DebugOutput)
                                {
                                    outputBuffer.AppendLine($"Using chunks of size {bufferSize}");
                                }

                                string base64Fragment;
                                int count = 0;
                                byte[] buffer = null;
                                while ((base64Fragment = await fs.ReadAsync(0, bufferSize, System.Threading.CancellationToken.None)).Length != 0)
                                {
                                    buffer = Convert.FromBase64String(base64Fragment);
                                    count = buffer.Length;
                                    if (DebugOutput)
                                    {
                                        outputBuffer.AppendLine($"Hashing {buffer.Length} bytes. {fs.Position} / {fs.Length}");
                                        var progress = Math.Floor(((decimal)fs.Position * 100) / fs.Length);
                                        if (progress > (lastAnnounce + 10))
                                        {
                                            stopWatch.Stop();
                                            lastAnnounce = progress;
                                            Output += $"{stopWatch.ElapsedMilliseconds}ms: {progress}%...{nl}";
                                            await Task.Delay(1);
                                            await this.InvokeAsync(this.StateHasChanged);
                                            stopWatch.Start();
                                        }
                                    }
                                    hash.TransformBlock(buffer, 0, count, buffer, 0);
                                    buffer = new byte[0];
                                    count = 0;
                                }
                                hash.TransformFinalBlock(buffer, 0, count);
                            }
                        }
                        else
                        {
                            using (var fs = await file.OpenReadAsync())
                            {
                                var bufferSize = UseBufferSize ? BufferSize : 4096 * 8;
                                if (DebugOutput)
                                {
                                    outputBuffer.AppendLine($"Using chunks of size {bufferSize}");
                                }
                                var buffer = new byte[bufferSize];
                                int count;

                                while ((count = await fs.ReadAsync(buffer, 0, buffer.Length)) != 0)
                                {
                                    if (DebugOutput)
                                    {
                                        outputBuffer.AppendLine($"Hashing {count} bytes. {fs.Position} / {fs.Length}");
                                    }
                                    var progress = Math.Floor(((decimal)fs.Position * 100) / fs.Length);
                                    if (progress > (lastAnnounce + 10))
                                    {
                                        stopWatch.Stop();
                                        lastAnnounce = progress;
                                        Output += $"{stopWatch.ElapsedMilliseconds}ms: {progress}%...{nl}";
                                        await Task.Delay(1);
                                        await this.InvokeAsync(this.StateHasChanged);
                                        stopWatch.Start();
                                    }

                                    hash.TransformBlock(buffer, 0, count, buffer, 0);
                                }
                                hash.TransformFinalBlock(buffer, 0, count);
                            }
                        }
                    }
                    var sb = new StringBuilder(hash.HashSize / 4);
                    foreach (var b in hash.Hash)
                    {
                        sb.AppendFormat("{0:x2}", b);
                    }

                    stopWatch.Stop();
                    if (DebugOutput)
                    {
                        Output += $"Done hashing file {fileInfo.Name} in {stopWatch.ElapsedMilliseconds}ms:{nl}";
                    }

                    Output += sb.ToString();
                    if (outputBuffer.Length > 0)
                    {
                        Output += $"{nl}{nl}Debug output:{nl}";
                        Output += outputBuffer.ToString();
                    }
                }
            }

        }
    }
